namespace SharpTalk.Core.Diagnostic
{
    using System;
    using System.Diagnostics;
    using System.Reflection;

    /// <summary>
    /// TraceProvider implementation class
    /// </summary>
    public sealed class ApplicationTraceProvider : ITraceProvider
    {
        private const string PREFIX_ERROR = "ERROR: ";
        private const string PREFIX_DEBUG = "DEBUG: ";
        private const string PREFIX_INFO  = "INFOS: ";
        private const string PREFIX_EXCEPTION = "EXCEPTION: ";
        private const string PREFIX_CALL = "CALL: ";

        private readonly TraceSource _traceSource = null;

        /// <summary>
        /// Creates a new instance of the <see cref="T:SharpTalk.Core.Diagnostic.ApplicationTraceProvider"/> class.
        /// </summary>
        /// <param name="sourceName">The name of the source</param>
        /// <returns>A new instance of <see cref="T:SharpTalk.Core.Diagnostic.ApplicationTraceProvider"/></returns>
        public static ITraceProvider CreateNew(string sourceName)
        {
            return CreateNew(sourceName, SourceLevels.All, new CustomTraceListener(string.Format("{0}.log", sourceName)));
        }

        /// <summary>
        /// Creates a new instance of the <see cref="T:SharpTalk.Core.Diagnostic.ApplicationTraceProvider"/> class.
        /// </summary>
        /// <param name="sourceName">The name of the source</param>
        /// <param name="level">The default level of traces to filter</param>
        /// <param name="listener"></param>
        /// <returns>A new instance of <see cref="T:SharpTalk.Core.Diagnostic.ApplicationTraceProvider"/></returns>
        public static ITraceProvider CreateNew(string sourceName, SourceLevels level, TraceListener listener)
        {
            var source = new TraceSource(sourceName, level);
            source.Listeners.Add(listener);
            return new ApplicationTraceProvider(source);
        }
        /// <summary>
        /// Private constructor. Do not use.
        /// </summary>
        private ApplicationTraceProvider(){ throw new InvalidOperationException(); }
        /// <summary>
        /// Creates a new instance.
        /// </summary>
        /// <param name="source">The <see cref="T:System.Diagnostics.TraceSource"/> to use to trace events.</param>
        private ApplicationTraceProvider(TraceSource source)
        {
            _traceSource = source;
        }

        /// <summary>
        /// Defines the SourceLevel
        /// </summary>
        /// <param name="level"></param>
        public void SetSourceLevel(SourceLevels level)
        {
            _traceSource.Switch.Level = level;
        }
        /// <summary>
        /// Traces an error
        /// </summary>
        /// <param name="message">The message to trace</param>
        public void TraceError(string message)
        {
            TraceEvent(TraceEventType.Error, PREFIX_ERROR + message);
        }
        /// <summary>
        /// Traces an error
        /// </summary>
        public void TraceError(string format, params object[] args)
        {
            TraceEvent(TraceEventType.Error, PREFIX_ERROR + format, args);
        }
        /// <summary>
        /// Traces an exception
        /// </summary>
        /// <param name="method">The method called.</param>
        /// <param name="exception">The exception thrown.</param>
        public void TraceException(MethodBase method, Exception exception)
        {
            TraceException(method,exception,false);
        }
        /// <summary>
        /// Traces an exception
        /// </summary>
        /// <param name="method">The method called.</param>
        /// <param name="exception">The exception thrown.</param>
        /// <param name="includeStack">Defines if the stackTrace should be included.</param>
        public void TraceException(MethodBase method, Exception exception, bool includeStack)
        {
            if (!_traceSource.Switch.ShouldTrace(TraceEventType.Error)) return;

            TraceEvent(TraceEventType.Error, "{0}{1} raised an exception of type {2}. Message: {3}.{4}",
                    PREFIX_EXCEPTION,
                    method == null
                        ? "Unknown method"
                        : String.Format("{0}.{1}", method.ReflectedType.FullName, method.Name),
                    exception.GetType(),
                    exception.Message + GetInnerExceptionMessages(exception.InnerException),
                    includeStack ? String.Format("\r\nStackTrace:{0}", exception.StackTrace) : null);
        }
        /// <summary>
        /// Concatenates all inner Exception messages
        /// </summary>
        /// <param name="innerException"></param>
        /// <returns></returns>
        private static string GetInnerExceptionMessages(Exception innerException)
        {
            var sb = new System.Text.StringBuilder();
            while (innerException != null)
            {
                sb.AppendFormat("\r\nInnerException: Type: {0}, Message: {1}", innerException.GetType(), innerException.Message);
                innerException = innerException.InnerException;
            }
            return sb.Length > 0 ? sb.ToString() : null;
        }
        /// <summary>
        /// Traces an information info
        /// </summary>
        /// <param name="message">The message to trace</param>
        public void TraceInfo(string message)
        {
            TraceEvent(TraceEventType.Information, PREFIX_INFO + message);
        }
        /// <summary>
        /// Traces an information info
        /// </summary>
        public void TraceInfo(string format, params object[] args)
        {
            TraceEvent(TraceEventType.Information, PREFIX_INFO + format, args);
        }
        /// <summary>
        /// Traces a verbose message
        /// </summary>
        /// <param name="message">The message to trace</param>
        public void TraceDebug(string message)
        {
            TraceEvent(TraceEventType.Verbose, PREFIX_DEBUG + message);
        }
        /// <summary>
        /// Traces a verbose message
        /// </summary>
        public void TraceDebug(string format, params object[] args)
        {
            TraceEvent(TraceEventType.Verbose, PREFIX_DEBUG + format, args);
        }
        /// <summary>
        /// Traces a method call
        /// </summary>
        /// <param name="method">The method called</param>
        public void TraceMethodCall(MethodBase method)
        {
            if (method == null || !_traceSource.Switch.ShouldTrace(TraceEventType.Verbose)) return;
            TraceEvent(TraceEventType.Verbose, "{0}{1}.{2}", PREFIX_CALL, method.ReflectedType.FullName, method.Name);
        }
        /// <summary>
        /// Traces an event
        /// </summary>
        /// <param name="type">The type of event</param>
        /// <param name="message">The message to trace</param>
        public void TraceEvent(TraceEventType type, string message)
        {
            if (_traceSource != null && _traceSource.Switch.ShouldTrace(type))
                _traceSource.TraceEvent(type, 0, message);
        }
        /// <summary>
        /// Traces an event
        /// </summary>
        public void TraceEvent(TraceEventType type, string format, params object[] args)
        {
            if(_traceSource !=null && _traceSource.Switch.ShouldTrace(type))
                _traceSource.TraceEvent(type,0,format, args);
        }
        /// <summary>
        /// Determines if an Event should be traced
        /// </summary>
        /// <param name="type">The type of event</param>
        /// <returns>True if the event is traced othrewise false</returns>
        public bool ShouldTrace(TraceEventType type)
        {
            return _traceSource != null && _traceSource.Switch.ShouldTrace(type);
        }
    }
}